﻿/* Creating a repository class for each entity type could result in a lot of redundant code, 
 * and it could result in partial updates. For example, suppose you have to update two different 
 * entity types as part of the same transaction.  If each uses a separate database context instance, 
 * one might succeed and the other might fail. One way to minimize redundant code is to use a generic repository, 
 * and one way to ensure that all repositories use the same database context (and thus coordinate all updates) 
 * It is to use a unit of work class. 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
//using FingerPrint.WebUI.Concrete;
using System.Linq.Expressions;
using System.Data;

namespace FingerPrint.WebUI.DAL
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        // Class variables are declared for the database context and for the entity set that the repository is instantiated for
        internal FingerPrintContext context;
        internal DbSet<TEntity> dbSet;

        // The constructor accepts a database context instance and initializes the entity set variable
        public GenericRepository(FingerPrintContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }


        /* The Get method uses lambda expressions to allow the calling code to specify a filter condition and a column to 
        * order the results by, and a string parameter lets the caller provide a comma-delimited list of navigation properties 
        * for eager loading.
        * 
        * The code Expression<Func<TEntity, bool>> filter means the caller will provide a lambda expression based on the TEntity type, 
        * and this expression will return a Boolean value. For example, if the repository is instantiated for the Student entity type, 
        * the code in the calling method might specify student => student.LastName == "Smith" for the filter parameter. 
        * 
        * The code Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy also means the caller will provide a lambda expression. 
        * But in this case, the input to the expression is an IQueryable object for the TEntity type. 
        * The expression will return an ordered version of that IQueryable object. For example, if the repository is instantiated for 
        * the Student entity type, the code in the calling method might specify q => q.OrderBy(s => s.LastName) for the orderBy parameter.
        */
        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            // The code in the Get method creates an IQueryable object and then applies the filter expression if there is one
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            // Next it applies the eager-loading expressions after parsing the comma-delimited list
            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            // Finally, it applies the orderBy expression if there is one and returns the results; otherwise it returns the results from the unordered query:
            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }
        /* When you call the Get method, you could do filtering and sorting on the IEnumerable collection returned by the method instead of providing parameters for these functions. 
        * But the sorting and filtering work would then be done in memory on the web server. By using these parameters, you ensure that the work is done by the database rather than the web server. 
        * An alternative is to create derived classes for specific entity types and add specialized Get methods, such as GetStudentsInNameOrder or GetStudentsByName. 
        * However, in a complex application, this can result in a large number of such derived classes and specialized methods, which could be more work to maintain.
        */


        /* The code in the GetByID, Insert, and Update methods is similar to what you saw in the non-generic repository. 
        * (You aren't providing an eager loading parameter in the GetByID signature, because you can't do eager loading with the Find method.)
        */
        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }


        /* Two overloads are provided for the Delete method. One of these lets you pass in just the ID of the entity to be deleted, 
         * and one takes an entity instance. For concurrency handling, you need a Delete method that takes an entity instance that 
         * includes the original value of a tracking property.
         */
        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    }

}